﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace MSNGame1
{
    /// <summary>
    /// Wrapper Class for window.external.Channel
    /// Provides connection functionality. 
    /// </summary>
    [ScriptableType]
    public class Channel
    {
        ScriptObject channelObject;
        
        /// <summary>
        /// Status of the connection to the other player.
        /// </summary>
        public ConnectionType Connection { get { return (ConnectionType)Enum.ToObject(typeof(ConnectionType), (int)(double)channelObject.GetProperty("Type")); } }
        
        /// <summary>
        /// Contains Data received during the last ReceivedData event
        /// </summary>
        public string Data { get { return (string)channelObject.GetProperty("Data"); } }
        
        /// <summary>
        /// Contains the last IM received.
        /// </summary>
        public string IM { get { return (string)channelObject.GetProperty("IM"); } }
        
        /// <summary>
        /// Contains the last error occured on the DataError Event.
        /// </summary>
        public ChannelError Error { get { return new ChannelError((ScriptObject)channelObject.GetProperty("Error")); } }

        /// <summary>
        /// Invoked when this client receives a message sent using SendData
        /// </summary>
        public event EventHandler<DataArgs> DataReceived;

        /// <summary>
        /// Invoked when this client receives an IM
        /// </summary>
        public event EventHandler<DataArgs> IMReceived;
        
        /// <summary>
        /// Invoked when SendData failed to transfer data to remote client
        /// </summary>
        public event EventHandler DataError;

        /// <summary>
        /// Invoked when the status of the connection is changed. Subscribe to this to know if you were Disconnected or routed through MSN's Servers (Indirect)
        /// Is also invoked when connection is first established
        /// </summary>
        public event EventHandler ConnectionChanged;
        
        /// <summary>
        /// Invoked when the local Activity is closed
        /// </summary>
        public event EventHandler AppClosed;

        /// <summary>
        /// Invoked when the Remote Client closed his Activity
        /// </summary>
        public event EventHandler RemoteAppClosed;

        /// <summary>
        /// Invoked when the remote client has finished loading the Activity, is connected to the local client and reeady to receive data/messages.
        /// </summary>
        public event EventHandler RemoteAppLoaded;
        
        /// <summary>
        /// Instanciates the wrapper.
        /// </summary>
        public Channel()
        {
        }

        /// <summary>
        /// Register the SilverLight channel object to the JavaScript Engine of the Browser (Initializes the connection!)
        /// Injects JavaScript into the page, in order to handle MSN Activity Events.
        /// Must be called!
        /// </summary>
        public void Initialize()
        {
            channelObject = (ScriptObject)((ScriptObject)HtmlPage.Window.GetProperty("external")).GetProperty("Channel");

            if (channelObject == null)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    System.Diagnostics.Debug.WriteLine("Could not initialize channel object.");
                    return;
                }

                throw new Exception("Could not find \"window.external.Channel\". Did you forget to run this through Messenger?");
            }

            string setPlugin = "";
            // Handle the special case that is, that the SilverLight control does not have an ID.
            if (string.IsNullOrEmpty(HtmlPage.Plugin.Id))
            {
                HtmlPage.Plugin.SetAttribute("ID", "SL1");
                int i = 0;
                foreach (HtmlElement he in HtmlPage.Document.GetElementsByTagName("Object"))
                {
                    if (he.Id == HtmlPage.Plugin.Id)
                    {
                        break;
                    }
                    i++;
                }
                setPlugin = @"
                    var objects = document.getElementsByTagName('object'); 
                    var plugin = objects[" + i + @"]";
            }
            else
            {
                setPlugin = @"var plugin = document.getElementById('" + HtmlPage.Plugin.Id + @"');";
            }
            
            JSCallback callback = new JSCallback(this);
            HtmlPage.RegisterScriptableObject("silverLightChannel", callback);

            

            HtmlPage.Window.Eval(@"
            
            "+ setPlugin + @"
            
            var silverLightChannel = plugin.Content.silverLightChannel;

            function Channel_OnRemoteAppLoaded() 
            {
                silverLightChannel.OnTypeChanged();
                silverLightChannel.OnRemoteAppLoaded();
            }

            function Channel_OnRemoteAppClosed() 
            {
                silverLightChannel.OnRemoteAppClosed();
            }

            function Channel_OnDataReceived() 
            {
                silverLightChannel.OnDataReceived();
            }

            function Channel_OnDataError() 
            {
                silverLightChannel.OnDataError();
            }

            function Channel_OnTypeChanged() 
            {
                silverLightChannel.OnTypeChanged();
            }

            function Channel_OnAppClose() 
            {
                silverLightChannel.OnAppClose();
            }

            function Channel_OnIMReceived() 
            {
                silverLightChannel.OnIMReceived();
            }

            ");
            channelObject.Invoke("Initialize");
        }

        /// <summary>
        /// Sends an IM, sender is the name of the Activity
        /// </summary>
        /// <param name="data">The message to send</param>
        public void SendIM(string message)
        {
            channelObject.Invoke("SendIM", message);
        }

        /// <summary>
        /// Enter some text into the Messenger textbox.
        /// </summary>
        /// <param name="data">The text to write to the textbox</param>
        public void EnterIM(string message)
        {
            channelObject.Invoke("EnterIM", message);
        }

        /// <summary>
        /// Sends an IM, the sender in this case is You.
        /// </summary>
        /// <param name="data">Message</param>
        public void SendIMAsUser(string message)
        {
            channelObject.Invoke("SendIMAsUser", message);
        }

        /// <summary>
        /// Sends a string to the other player. If the connection is Indirect you are only allowed to send 120 packets every minute (2 pr second)
        /// </summary>
        /// <param name="data">The data to be sent</param>
        public void SendData(string data)
        {
            string retVal = (string)channelObject.Invoke("SendData", data);
            if (string.IsNullOrEmpty(retVal))
                return;

            throw new Exception(retVal);
        }

        /// <summary>
        /// Sends bytes to the other player encoding them into a Base64 string. If the connection is Indirect you are only allowed to send 120 packets every minute (2 pr second)
        /// </summary>
        /// <param name="data">The data to be sent</param>
        public void SendData(byte[] data)
        {
            string data2 = Convert.ToBase64String(data);
            SendData(data2);
        }

        protected void OnDataReceived()
        {
            if (DataReceived != null)
                DataReceived(this, new DataArgs(Data));
        }

        protected void OnIMReceived()
        {
            if (IMReceived != null)
                IMReceived(this, new DataArgs(IM));
        }

        protected void OnTypeChanged()
        {
            if (ConnectionChanged != null)
                ConnectionChanged(this, EventArgs.Empty);
        }

        protected void OnAppClose()
        {
            if (AppClosed != null)
                AppClosed(this, EventArgs.Empty);
        }

        protected void OnRemoteAppClosed()
        {
            if (RemoteAppClosed != null)
                RemoteAppClosed(this, EventArgs.Empty);
        }

        protected void OnRemoteAppLoaded()
        {
            if (RemoteAppLoaded != null)
                RemoteAppLoaded(this, EventArgs.Empty);
        }

        protected void OnDataError()
        {
            if (DataError != null)  
                DataError(this, EventArgs.Empty);
        }

        /// <summary>
        /// Contains a string to send with a received data event
        /// </summary>
        public class DataArgs : EventArgs { public string Data { get; set; } public DataArgs(string data) { this.Data = data; } }
        
        /// <summary>
        /// Connection type to other player
        /// </summary>
        public enum ConnectionType : int
        {
            /// <summary>
            /// A Direct connection, which means you can send however many packets you want
            /// </summary>
            Direct = 0,
            /// <summary>
            /// An Indirect connection routes over MSN servers and traffic is limited to 120 packets/min (2 packets/sec)
            /// </summary>
            Indirect = 1,
            /// <summary>
            /// No connection to other player.
            /// </summary>
            Disconnected = 2
        }
        
        /// <summary>
        /// Wrapper for window.external.Channel.Error
        /// </summary>
        public class ChannelError
        {
            ScriptObject errorObject;

            /// <summary>
            /// A string containing the data of the transaction that failed during SendData.
            /// </summary>
            public string Data { get { return (string)errorObject.GetProperty("Data"); } }
            /// <summary>
            ///  Int value representing the type of error that occurred. (No actual data on which values means what :()
            /// </summary>
            public int Type { get { return (int)(double)errorObject.GetProperty("Type"); } }

            internal ChannelError(ScriptObject errorObject)
            {
                this.errorObject = errorObject;
            }

            public override bool Equals(object obj)
            {
                return errorObject == ((ChannelError)obj).errorObject;
            }

            public override int GetHashCode()
            {
                return errorObject.GetHashCode();
            }
        }

        public class JSCallback
        {
            Channel c;
            internal JSCallback(Channel c)
            {
                this.c = c;
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnDataReceived()
            {
                c.OnDataReceived();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnIMReceived()
            {
                c.OnIMReceived();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnTypeChanged()
            {
                c.OnTypeChanged();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnAppClose()
            {
                c.OnAppClose();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnRemoteAppClosed()
            {
                c.OnRemoteAppClosed();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnRemoteAppLoaded()
            {
                c.OnRemoteAppLoaded();
            }

            /// <summary>
            /// Called from JavaScript, invokes the corresponding Event!
            /// </summary>
            [ScriptableMember]
            public void OnDataError()
            {
                c.OnDataError();
            }

        }

    }

}
